Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SecuritySolution] Retrieve onboarding steps #181442

Merged
merged 43 commits into from
May 28, 2024
Merged

Conversation

angorayc
Copy link
Contributor

@angorayc angorayc commented Apr 23, 2024

Summary

Fixes #181190
Relevant PRs: #143598 | #163739

Steps to Verify:

  1. Entering these in your kibana.dev.yml
  2. Execute this command to set the Guided onboarding steps to alertsCases
curl --location --request PUT 'http://localhost:5601/internal/guided_onboarding/state' \
--header 'kbn-xsrf: cypress-creds' \
--header 'x-elastic-internal-origin: security-solution' \
--header 'Content-Type: application/json' \
--header 'Authorization: Basic {YOUR_AUTH_TOKEN}' \.    // If you are using Postman just fill in the Auth tab
--data '{
  "status": "in_progress",
  "guide": {
    "isActive": true,
    "status": "in_progress",
    "steps": [
      { "id": "add_data", "status": "complete" },
      { "id": "rules", "status": "complete" },
      { "id": "alertsCases", "status": "active" }
    ],
    "guideId": "siem"
  }
}'
  1. Make sure you have alerts available.
  2. To test the old flyout with Guided onboarding tour, please go to Stack Management > Advanced settings > Expandable flyout OFF

It compatible with the new expandable flyout:

  1. It shows expandable flyout tour when the guided onboarding tour is not enabled.
  2. The first two steps should be hidden when the left expandable is opened.
  3. Most of the guided onboarding tour steps should be hidden when Add to new case flyout or Add to existing case modal opened.
  4. Once the test case is created, the insight section and correlation tab should be opened automatically to fetch cases.
  5. expandable flyout tour should be visible again after the guided onboarding tour is finished.
Screen.Recording.2024-05-03.at.14.30.03.mov

It compatible with the old flyout:

Screen.Recording.2024-05-01.at.21.19.29.mov

Checklist

Delete any items that are not applicable to this PR.

@@ -469,23 +476,17 @@ const EventDetailsComponent: React.FC<Props> = ({
);

return (
<GuidedOnboardingTourStep
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only wrap only the target tab with the GuidedOnboardingTourStep to avoid adding extra attributes to other tabs in their DOM elements and cause the tour anchor locate incorrectly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2024-05-01 at 14 51 28

const isAddToNewCaseFlyoutOpen = cases.hooks.useIsAddToNewCaseFlyoutOpen();
const isAddToExistingCaseModalOpen = cases.hooks.useAddToExistingCaseModalOpen();

const hiddenWhenCasesModalFlyoutExpanded = useMemo(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid this scenario:
Screenshot 2024-05-01 at 14 38 13

@@ -190,6 +192,8 @@ export class CasesUiPlugin
hooks: {
useCasesAddToNewCaseFlyout,
useCasesAddToExistingCaseModal,
useIsAddToNewCaseFlyoutOpen,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please find our use cases here

</CellTooltipWrapper>
),
render: (value: string, caseData: RelatedCase) => {
const index = data.findIndex((d) => d.id === caseData.id);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the existing guided onboarding tour's logic, it only shows the tour step if index equals to 0

const { activeStep, isTourShown } = useTourContext();
const isGuidedOnboardingTourShown =
isTourShown(SecurityStepId.alertsCases) && activeStep === AlertsCasesTourSteps.viewCase;
const expanded =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expand the insight section automatically after the test case created, so the relevant cases can be fetched.

);

const renderTabs = tabs.map((tab, index) =>
isAlert && tab.id === 'overview' ? (
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const onAddExceptionTypeClick = useCallback(
(exceptionListType?: ExceptionListTypeEnum): void => {
setExceptionFlyoutType(exceptionListType ?? null);
setAllTourStepsHidden(true);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hide the tour when flyout open to avoid this situation:

Screenshot 2024-05-02 at 09 29 50

@angorayc angorayc added Team:Threat Hunting Security Solution Threat Hunting Team Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:Threat Hunting:Investigations Security Solution Investigations Team Team:Threat Hunting:Explore v8.15.0 labels May 2, 2024
@angorayc
Copy link
Contributor Author

angorayc commented May 2, 2024

buildkite test this

@angorayc
Copy link
Contributor Author

angorayc commented May 7, 2024

/ci

@angorayc angorayc changed the title Retrieve onboarding steps [SecuritySolution] Retrieve onboarding steps May 7, 2024
@angorayc
Copy link
Contributor Author

angorayc commented May 7, 2024

buildkite test this

@angorayc
Copy link
Contributor Author

angorayc commented May 7, 2024

buildkite test this

@semd
Copy link
Contributor

semd commented May 21, 2024

@angorayc I added React.memo to the SecurityTourStep component, it prevents some unnecessary updates from happening: 05784f5

@semd
Copy link
Contributor

semd commented May 21, 2024

Hi @PhilippeOberti, I've been checking the re-renders you mentioned. It's an interesting point, the reason we see the table cells highlighted is not the DefaultCellRenderer component being re-rendered, it's the useHiddenByFlyout hook that receives an update from the useLocation() call, due to the flyout URL state change.

This hook is the only code that gets re-executed, it needs to check if the flyout "expanded" value has changed, there's no way around that if we want to have a tour anchor inside a table cells.

The good news is that the only component that updates is the GuidedOnboardingTourStep wrapper. The DefaultCellRenderer children component never receives any update when the flyout state changes. On top of that, I made a small change (2534c07) so the only GuidedOnboardingTourStep cell that updates is the one that has the anchor, the rest of the cells are rendered directly:

tour_updates_only_anchor.mov

@angorayc angorayc requested a review from PhilippeOberti May 21, 2024 10:23
@angorayc
Copy link
Contributor Author

/ci

Copy link
Contributor

@kqualters-elastic kqualters-elastic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems like a lot of changes to many different cross cutting concerns to fix something that I think could/should be done in the onboarding code, but the re-renders are no longer as bad as they were, lgtm 👍

Copy link
Member

@cnasikas cnasikas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the reasoning behind adding the casesContextState$ in the cases context provider value but I think it breaks the encapsulation between the internal state and the value of the provider. Instead of exposing the state this way, what do you think about passing a callback to the provider? Example:

export const CasesProvider: FC<
  PropsWithChildren<{
    value: CasesContextProps;
    queryClient?: QueryClient;
  }>
> = ({
  children,
  value: {
    externalReferenceAttachmentTypeRegistry,
    persistableStateAttachmentTypeRegistry,
    owner,
    permissions,
    basePath = DEFAULT_BASE_PATH,
    features = {},
    releasePhase = 'ga',
    getFilesClient,
  },
  queryClient = casesQueryClient,
  onToggleFlyout,
}) => {
  const [state, dispatch] = useReducer(casesContextReducer, getInitialCasesContextState());

  const onDispatch = (args) => {
    if (args.type === CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT ) {
      onToggleFlyout({isFlyoutOpen: true})
    }

    onDispatch(args)
  };
  
  // rest of code
  
  };

@semd
Copy link
Contributor

semd commented May 24, 2024

Hey @cnasikas yes, using the observable inside the context is not a conventional approach, I understand why you prefer the onToggleFlyout callback, but I don't think this is a very good approach either, to get what we need it would look like this:

  const [state, _dispatch] = useReducer(casesContextReducer, getInitialCasesContextState());

  const dispatch: CasesContextValueDispatch = useCallback(
    (args) => {
      _dispatch(args);
      if (
        args.type === CasesContextStoreActionsList.OPEN_CREATE_CASE_FLYOUT ||
        args.type === CasesContextStoreActionsList.OPEN_ADD_TO_CASE_MODAL
      ) {
        onToggleAddToCase({ isOpen: true });
      } else if (
        args.type === CasesContextStoreActionsList.CLOSE_CREATE_CASE_FLYOUT ||
        args.type === CasesContextStoreActionsList.CLOSE_ADD_TO_CASE_MODAL
      ) {
        onToggleAddToCase({ isOpen: false });
      }
    },
    [onToggleAddToCase]
  );

  // add `dispatch` function to the context 

This is a very specific implementation for what we need right now, it'd need to be extracted to a new "side-effects" module to make it extensible, and the onToggleAddToCase prop could cause context re-renders if it gets updated from the consumer. Also, from our side, it's much harder to use, we'd need to keep this value in a new state (isAddToCaseOpen) at the CasesContext level (applications top level), and somehow make it available to be read from the GuidedTour component, which would have some other complexities.

As an alternative, I have a third proposal:
Leave the current context alone and create a separate context to store only the state so we don't interfere with the Cases components that currently use the existing CasesContext for static information. The new state context will only be used by the new useIsAddToCaseOpen hook that is exposed from the cases plugin (more hooks like this one can be added later), so only the components that use this hook (our security GuidedTour components) will be re-rendered when the state changes. I made the changes here: 96f5924

This approach is explained in the react documentation docs. I think this is cleaner than the other 2 options, more reusable, does not mess up with the current context, and is very easy to use for us.
What do you think?

@cnasikas
Copy link
Member

@semd I really like the new proposal. I knew there was something better but I could not figure it out. Thanks! Does the order of context providers matter? Also should we add the new context provider here x-pack/plugins/cases/public/common/mock/test_providers.tsx? I think we should not but I am curious to here your opinion.

@semd
Copy link
Contributor

semd commented May 24, 2024

@cnasikas

I really like the new proposal. I knew there was something better but I could not figure it out

Great! Yes, Angela and I have been discussing this code for quite a while, and the observable was my idea lol, but none of us were convinced about it. Creating a separate context for the state makes a lot more sense.

Does the order of context providers matter?

Nope. The only thing that matters is that the provider wraps the consumer at some level.

Also should we add the new context provider here x-pack/plugins/cases/public/common/mock/test_providers.tsx? I think we should not but I am curious to here your opinion.

Exactly, since the CasesProvider already renders the CasesStateContext, there's no need to do anything else.

@cnasikas
Copy link
Member

@semd @angorayc Thank you both! I will review it again today.

Copy link
Member

@cnasikas cnasikas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for your willingness to help and find a new solution! LGMT!

@semd
Copy link
Contributor

semd commented May 24, 2024

@elasticmachine merge upstream

@semd
Copy link
Contributor

semd commented May 27, 2024

@elasticmachine merge upstream

@kibana-ci
Copy link
Collaborator

💚 Build Succeeded

Metrics [docs]

Module Count

Fewer modules leads to a faster build time

id before after diff
cases 753 755 +2
securitySolution 5483 5484 +1
total +3

Async chunks

Total size of all lazy-loaded chunks that will be downloaded as the user navigates the app

id before after diff
securitySolution 15.1MB 15.2MB +113.7KB

Public APIs missing exports

Total count of every type that is part of your API that should be exported but is not. This will cause broken links in the API documentation system. Target amount is 0. Run node scripts/build_api_docs --plugin [yourplugin] --stats exports for more detailed information.

id before after diff
cases 27 28 +1

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
cases 150.9KB 151.5KB +586.0B
securitySolution 83.6KB 83.7KB +55.0B
total +641.0B

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@angorayc angorayc merged commit 5d0f09a into elastic:main May 28, 2024
37 checks passed
@kibanamachine kibanamachine added the backport:skip This commit does not require backporting label May 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:skip This commit does not require backporting release_note:skip Skip the PR/issue when compiling release notes Team: SecuritySolution Security Solutions Team working on SIEM, Endpoint, Timeline, Resolver, etc. Team:Threat Hunting:Explore Team:Threat Hunting:Investigations Security Solution Investigations Team Team:Threat Hunting Security Solution Threat Hunting Team v8.15.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[SecuritySolution] Guided onboarding is not working with the new flyout
9 participants